#ifndef SeparableSSS_SSSSRender_h
#define SeparableSSS_SSSSRender_h

#include <cstdlib>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <sstream>

#import <GLKit/GLKit.h>

#include "Timer.h"
#include "Shader.h"
#include "Model.h"
#include "Debug.h"
#include "Camera.h"
#include "RenderContex.h"
#include "TextureLoader.h"
#include "GLError.h"

#include "RenderEngine.h"
#include "Light.hpp"

#include "Helper.h"

#include "SeparableSSS.h"
#include "Bloom.h"
#include "DepthOfField.h"
#include "FilmGrain.h"

#define CAMERA_FOV 20.0f
#define PI 3.1415926536f

#define N_LIGHTS 3

using std::string;
using glm::vec2;
using glm::vec3;
using glm::vec4;
using glm::mat4;


class SSSSRender
{
public:
    SSSSRender()
    {
        
    }
    
    bool initialize(int width, int height, GLKView* view)
    {
        RenderEngine::_view = view;
        
        _width = width;
        _height = height;
        RenderContex::set_window_size(_width, _height);
        
        shader_main.init(IOS_PATH("shader", "Main", "vert"), IOS_PATH("shader", "Main", "frag"));
        shader_sky_dome.init(IOS_PATH("shader", "SkyDome", "vert"), IOS_PATH("shader", "SkyDome", "frag"));
        _simple_shader.init(IOS_PATH("shader", "simple", "vert"), IOS_PATH("shader", "simple", "frag"));
        shader_shadow_map.init(IOS_PATH("shader", "ShadowMap", "vert"), IOS_PATH("shader", "ShadowMap", "frag"));
        
        // Load the texture
        tex_head_diffuse = TextureLoader::CreateTexture(IOS_PATH("head", "DiffuseMap_R8G8B8A8_1024_mipmaps", "dds").c_str(), true);
        tex_head_specularAO = TextureLoader::CreateTexture(IOS_PATH("head", "SpecularAOMap", "dds").c_str());
        tex_head_normal_map = TextureLoader::CreateTexture(IOS_PATH("head", "NormalMap_RG16f_1024_mipmaps", "dds").c_str());
        tex_sky = TextureLoader::CreateTextureCubemap(IOS_PATH("StPeters", "DiffuseMap", "dds").c_str());
        tex_sky_irradiance_map = TextureLoader::CreateTextureCubemap(IOS_PATH("StPeters", "IrradianceMap", "dds").c_str());
        tex_beckmann = TextureLoader::CreateTexture(IOS_PATH("Texture", "BeckmannMap", "dds").c_str());
        
        check_gl_error();
        
        _model_head.init(IOS_PATH("head", "head_optimized", "obj"), true, true, true, false);
        _model_sphere.init(IOS_PATH("Models", "Sphere", "obj"));
        
        ModelManager::static_init();
        ShaderManager::static_init();
        RenderEngine::static_init();
        RenderTarget::static_init();
        
        SeparableSSS::static_init();
        FilmGrain::static_init();
        DepthOfField::static_init();
        ShadowMap::static_init();
        Bloom::static_init();
        
        //_camera.setDistance(3.1f);
        //_camera.setAngle(vec2(-0.49005f, 0.0508272f));
        //_camera.setPanPosition(vec2(-0.0168704, 0.0729295));
        //_camera.setAngularVelocity(vec2(0, 0));
        _camera.setProjection(CAMERA_FOV * PI / 180.0f, 1.0f * _width / _height, 0.1f, 100.0f);
        //_camera.build();
        
        ssss.init(CAMERA_FOV, 0.012f, 17);
        //ssss.resize(_width, _height);
        bloom.init(Bloom::TONEMAP_FILMIC, exposure, 0.63f, 1.0f, 1.0f, 0.2f);
        dof.init(0.66f, 0.76f, vec2(15.0f, 15.0f), 2.5f);
        
        
        init_opengl();
        
        for (int i = 0; i < N_LIGHTS; i++)
        {
            lights[i].init();
        }
        
        load_preset(IOS_PATH("Preset", "Preset9", "txt"));
        
        return true;
    }
    
    void init_opengl()
    {
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LESS);
        glEnable(GL_CULL_FACE);
        glClearStencil(0x0);
        glEnable(GL_STENCIL_TEST);
        glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
        
        glClearColor(0, 0, 0, 0);
        
        //glEnable(GL_FRAMEBUFFER_SRGB);
        mainRT.init(_width, _height);
        depthRT.init(_width, _height, GL_R8, GL_RED);
        specularsRT.init(_width, _height);
        //velocityRT.init(_width, _height, GL_RG8, GL_RG);
        shadowRT.init();
        tempRT_SRGB.init(_width, _height, GL_SRGB8_ALPHA8);
        backBufferRT.init();
        
        depth_stencil.init();
    }
    
    void load_preset(std::string path)
    {
        std::ifstream fs(path);
        fs >> _camera;
        _camera.build();
        
        for (int i = 0; i < N_LIGHTS; i++)
        {
            fs >> lights[i];
        }
        
        fs.close();
    }
    
    void update(float tt)
    {
        static float t = Timer::now();
        float now = Timer::now();
        //if (_rotate)
            _camera.frameMove(now - t);
        t = now;
    }
    
    void main_pass()
    {
        glViewport(0, 0, _width, _height);
        RenderContex::camera = &_camera;
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        
        shader_main.use();
        check_gl_error();

        RenderContex::model_mat = glm::scale(mat4(1.0f), vec3(0.7f, 0.7f, 0.7f)) * glm::translate(mat4(1.0f), vec3(0, 0.2f, 0.425f));
        mat4 curr_mvp_mat = RenderContex::get_mvp_mat();
        
        shader_main.bind_mat4("curr_model_view_proj", curr_mvp_mat);
        //shader_main.bind_mat4("prev_model_view_proj", RenderContex::prev_mvp);        // TODO
        //shader_main.bind_mat4("prev_model_view_proj", curr_mvp_mat);
        shader_main.bind_mat4("Model", RenderContex::model_mat);
        shader_main.bind_mat4("ModelInverseTranspose", RenderContex::get_model_inverse_transpose());
        shader_main.bind_vec3("camera_position", _camera.getEyePosition());
        
        check_gl_error();

        shader_main.bind_texture("diffuse_tex", tex_head_diffuse, 0, GL_TEXTURE_2D);
        shader_main.bind_texture("specularAO_tex", tex_head_specularAO, 1, GL_TEXTURE_2D);
        shader_main.bind_texture("normal_map_tex", tex_head_normal_map, 2, GL_TEXTURE_2D);
        shader_main.bind_texture("beckmann_tex", tex_beckmann, 3, GL_TEXTURE_2D);
        shader_main.bind_texture("irradiance_tex", tex_sky_irradiance_map, 4, GL_TEXTURE_CUBE_MAP);
        shader_main.bind_float("specularIntensity", specularIntensity);
        shader_main.bind_float("specularRoughness", specularRoughness);
        shader_main.bind_float("specularFresnel", specularFresnel);
        shader_main.bind_float("bumpiness", bumpiness);
        shader_main.bind_float("ambient", ambient);
        
        shader_main.bind_bool("sssEnabled", enable_ssss);
        shader_main.bind_bool("separate_speculars", separate_speculars);
        shader_main.bind_bool("sssTranslucencyEnabled", enable_sss_translucency);
        shader_main.bind_float("sssWidth", sss_width);
        shader_main.bind_float("translucency", translucency);
        
        check_gl_error();
        
        static char name_buff[32];
        for (int i = 0; i < N_LIGHTS; i++)
        {
            auto& l = lights[i];
            auto& lc = l.camera;
            auto& pos = lc.getEyePosition();
            
            sprintf(name_buff, "lights[%d].position", i);
            shader_main.bind_vec3(name_buff, pos);
            
            auto dir = lc.getLookAtPosition() - pos;
            dir = glm::normalize(dir);
            sprintf(name_buff, "lights[%d].direction", i);
            shader_main.bind_vec3(name_buff, dir);
            
            sprintf(name_buff, "lights[%d].falloffStart", i);
            shader_main.bind_float(name_buff, cos(0.5f * l.fov));
            
            sprintf(name_buff, "lights[%d].falloffWidth", i);
            shader_main.bind_float(name_buff, falloff_width);
            
            l.color = l.intensity * vec3(1, 1, 1);
            sprintf(name_buff, "lights[%d].color", i);
            shader_main.bind_vec3(name_buff, l.color);
            
            sprintf(name_buff, "lights[%d].attenuation", i);
            shader_main.bind_float(name_buff, l.attenuation);
            
            sprintf(name_buff, "lights[%d].farPlane", i);
            shader_main.bind_float(name_buff, l.farPlane);
            
            sprintf(name_buff, "lights[%d].bias", i);
            shader_main.bind_float(name_buff, l.bias);
            
            sprintf(name_buff, "lights[%d].viewProjection", i);
            shader_main.bind_mat4(name_buff, ShadowMap::getViewProjectionTextureMatrix(lc.getViewMatrix(), lc.getProjectionMatrix()));
            
            sprintf(name_buff, "shadow_maps_%d", i);
            shader_main.bind_texture(name_buff, l.shadowMap.get_depth_texture(), 5 + i, GL_TEXTURE_2D);
            glBindSampler(5 + i, ShadowMap::sampler_object_for_shadow_map);
            
            sprintf(name_buff, "depth_textures_%d", i);
            shader_main.bind_texture(name_buff, l.shadowMap.get_depth_texture(), 5 + N_LIGHTS + i, GL_TEXTURE_2D);
            glBindSampler(5 + N_LIGHTS + i, ShadowMap::sampler_object_for_depth_texutre);
        }
        
        _model_head.render(true, true, true, false);
        
        RenderContex::prev_mvp = curr_mvp_mat;
        
        check_gl_error();
    }
    
    void sky_dome_pass()
    {
        check_gl_error();
        glViewport(0, 0, _width, _height);
        RenderContex::camera = &_camera;
        RenderContex::model_mat = glm::scale(glm::mat4(1.0f), glm::vec3(2.f));
        glEnable(GL_CULL_FACE);
        glCullFace(GL_FRONT);
        shader_sky_dome.use();
        shader_sky_dome.bind_mat4("MVP", RenderContex::get_mvp_mat());
        shader_sky_dome.bind_texture("skyTex", tex_sky, 0, GL_TEXTURE_CUBE_MAP);
        _model_sphere.render(false, false, false, false);
        glCullFace(GL_BACK);
        check_gl_error();
    }
    
    void shadow_pass()
    {
        auto model_mat = glm::scale(mat4(1.0f), vec3(0.7f, 0.7f, 0.7f)) *
        glm::translate(mat4(1.0f), vec3(0, 0.2f, 0.425f));
        
        for (int i = 0; i < N_LIGHTS; i++)
        {
            Light& l = lights[i];
            
            glViewport(0, 0, ShadowMap::SHADOW_MAP_SIZE, ShadowMap::SHADOW_MAP_SIZE);
            shader_shadow_map.use();
            
            l.shadowMap.bind();
            
            //glCullFace(GL_FRONT);
            glEnable(GL_POLYGON_OFFSET_FILL);
            glPolygonOffset(2.0f, 4.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            
            const mat4& view = l.camera.getViewMatrix();
            mat4 linear_proj = l.camera.getProjectionMatrix();
            
            mat4 mvp = linear_proj * view * model_mat;
            shader_shadow_map.bind_mat4("MVP", mvp);
            _model_head.render(false, false, false, false);
            
            l.shadowMap.unbind();
            
            glDisable(GL_POLYGON_OFFSET_FILL);
            //glCullFace(GL_BACK);
        }
        
    }
    
    void render()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        glViewport(0, 0, _width, _height);
        
        glEnable(GL_CULL_FACE);
        
        timer.start();
        
        // shadow
        glDisable(GL_STENCIL_TEST);
        glEnable(GL_DEPTH_TEST);
        shadow_pass();
        check_gl_error();
        
        timer.clock("Shadows");
        
        // main
        static RenderTexture* rts[] = { &mainRT, &depthRT, &specularsRT};
        RenderEngine::set_render_target(3, rts, &depth_stencil);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        glEnable(GL_STENCIL_TEST);
        glStencilFunc(GL_ALWAYS, 1, 0xff);
        glStencilMask(0xff);
        main_pass();
        check_gl_error();
        
        timer.clock("Main");
        
        // sky
        glDisable(GL_STENCIL_TEST);
        glStencilMask(0x00);
        RenderEngine::set_render_target(&mainRT, &depth_stencil);
        sky_dome_pass();
        check_gl_error();
        
        timer.clock("Sky");
        
        if (enable_ssss)
        {
            ssss.setStrength(sss_strength);
            ssss.setFalloff(sss_falloff);
            ssss.setWidth(sss_width);
            ssss.render(mainRT, depthRT, specularsRT, depth_stencil);
            
            timer.clock("Skin");
            
            if (separate_speculars)
            {
                // add specular
                glEnable(GL_BLEND);
                glBlendFunc(GL_ONE, GL_ONE);
                RenderEngine::set_render_target(&mainRT, nullptr);
                RenderEngine::draw_texture(specularsRT);
                //RenderEngine::set_render_target(nullptr, nullptr);
                glDisable(GL_BLEND);
                
                timer.clock("Separate Speculars");
            }
        }
        
        RenderTexture * rt_to_next_stage = nullptr;
        
        rt_to_next_stage = &mainRT;
        
        // Bloom
        if (enable_bloom)
        {
            bloom.render(&mainRT, &tempRT_SRGB);
            rt_to_next_stage = &tempRT_SRGB;
            timer.clock("Bloom");
        }
        
        // DoF
        if (enable_dof)
        {
            dof.set_focus_distance(_camera.getDistance() - 1.0f + focus_dist);
            dof.set_focus_falloff(focus_falloff);
            dof.set_focus_range(pow(focus_range, 5.0f));
            dof.render(*rt_to_next_stage, mainRT, depthRT);
            timer.clock("DOF");
            rt_to_next_stage = &mainRT;
        }
        
        // Film Grain
        static int t = 0;
        if (enable_film_grain)
        {
            film_grain.set_noise_intensity(film_grain_noise_intensity);
            film_grain.set_expose(exposure);
            t++;
            film_grain.render(*rt_to_next_stage, backBufferRT, t / 60.0f);
            timer.clock("FilmGrain");
            rt_to_next_stage = &backBufferRT;
        }
        
        rt_to_next_stage->render_to_screen();
        
        check_gl_error();
    }
    
    void render2()
    {
        timer.start();
        
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        glViewport(0, 0, _width, _height);
        
        _simple_shader.use();
        RenderContex::model_mat = mat4(1.0f);
        RenderContex::camera = &_camera;
        _simple_shader.bind_mat4("MVP", RenderContex::get_mvp_mat());
        
        _model_head.render();
        
        timer.clock("Simple");
    }
    
    static void shut_down()
    {
        
    }

    void set_enable_ssss(bool enabled = true)
    {
        enable_ssss = enabled;
    }
    
    void set_enable_bloom(bool enabled = true)
    {
        enable_bloom = enabled;
    }
    
    void set_enable_dof(bool enabled = true)
    {
        enable_dof = enabled;
    }
    
    void set_enable_film_grain(bool enabled = true)
    {
        enable_film_grain = enabled;
    }
    
    void set_enable_rotate(bool enabled = true)
    {
        _rotate = enabled;
    }
    
    void print_time()
    {
        std::stringstream ss;
        ss << timer << std:: endl;
        Debug::LogInfo(ss.str().c_str());
    }
    
    void touch_begin(float x, float y)
    {
        _camera.mousePos = vec2(x, y);
        _camera.draggingLeft = true;
        _camera.attenuation = 2.0f;
    }
    
    void touch_move(float x, float y)
    {
        vec2 new_mouse_pos = vec2(x, y);
        vec2 delta = _camera.mousePos - new_mouse_pos;
        
        delta = -delta;
        _camera.setAngularVelocity(_camera.getAngularVelocity() - delta);
        _camera.mousePos = new_mouse_pos;
    }
    
    void touch_end()
    {
        _camera.draggingLeft = false;
    }
    
    void scale(float s)
    {
        float d = _camera.getDistance() * s;
        if (d > 10) d = 10.0f;
        else if (d < 1.5) d = 1.5f;
        //printf("%f\n", d);
        _camera.setDistance(d);
    }
    
private:
    int _width;
    int _height;
    
    Timer timer;
    
    Camera _camera;
    
    bool _rotate = false;
    
    Light lights[N_LIGHTS];
    
    // resources
    Shader shader_sky_dome;
    Shader shader_shadow_map;
    Shader shader_main;
    Shader _simple_shader;
    
    // textures
    GLuint tex_head_diffuse;
    GLuint tex_head_specularAO;
    GLuint tex_head_normal_map;
    GLuint tex_sky;
    GLuint tex_sky_irradiance_map;
    GLuint tex_beckmann;
    
    Model _model_head;
    //Model _model_head_with_normal;
    Model _model_sphere;
    
    RenderTexture mainRT;
    RenderTexture depthRT;
    RenderTexture specularsRT;
    //RenderTexture velocityRT;
    RenderTexture shadowRT;
    RenderTexture tempRT_SRGB;
    RenderTexture backBufferRT;
    
    DepthStencil depth_stencil;
    
    
    bool separate_speculars = false;
    bool enable_ssss = true;
    bool enable_sss_translucency = true;
    float sss_width = 0.012f;
    vec3 sss_strength = vec3(0.48f, 0.41f, 0.28f);
    vec3 sss_falloff = vec3(1.0f, 0.37f, 0.3f);
    float translucency = 0.88f;	// TODO
    
    double speed = 1;
    float specularIntensity = 1.88f;
    float specularRoughness = 0.3f;
    float specularFresnel = 0.82f;
    float bumpiness = 0.9f;
    float ambient = 0.80f; // 0.61f
    
    float falloff_width = 0.1f;
    
    // bloom
    bool enable_bloom = true;
    
    //DOF
    bool enable_dof = true;
    float focus_dist = 0.66f;
    float focus_range = 0.76f;
    float focus_falloff = 15.0f;
    
    // film grain
    bool enable_film_grain = true;
    float film_grain_noise_intensity = 1.0f;
    float exposure = 2.0f;
    
    SeparableSSS ssss;
    Bloom bloom;
    DepthOfField dof;
    FilmGrain film_grain;
};


#endif
